/*
  Program: Mine Mayhem
  File:    custom.cc
  Date:    July, 1996 to February,   1997,    version 1.00
	   26 October to 3 December,	      version 2.00
	   8 December to 4 January,  2001,    version 2.10
	   15 January,			      version 2.11
	   18 September to 16 December, 2006, version 2.90

  The custom layout dialog box and popup menu.

  Freeware, Copyright 2006 Jason Hood.
*/

#include <tws/message.h>
#include "custom.h"
#include "minefld.h"

#define EDIT_ID_WIDTH   20001
#define EDIT_ID_HEIGHT  20002
#define EDIT_ID_MINE    20003

#define ID_STR          20004
#define ID_BEG          20005
#define ID_INT          20006
#define ID_EXP          20007
#define ID_RAN          20008
#define ID_RES		20009

#define ID_SAV		20010
#define ID_DEL		20011


DEFINE_RESPONSE_TABLE(CustomDlg, TabWin)

   E_SPINCHANGE(EDIT_ID_WIDTH,  cmChange)
   E_SPINCHANGE(EDIT_ID_HEIGHT, cmChange)
   E_SPINCHANGE(EDIT_ID_MINE,   cmChange)

   E_BUTTONUP(ID_STR, cmStr)
   E_BUTTONUP(ID_BEG, cmBeg)
   E_BUTTONUP(ID_INT, cmInt)
   E_BUTTONUP(ID_EXP, cmExp)
   E_BUTTONUP(ID_RAN, cmRan)
   E_BUTTONUP(ID_RES, cmRes)

   E_BUTTONUP(ID_SAV, cmSav)
   E_BUTTONUP(ID_DEL, cmDel)

   E_BUTTONUP(ID_OK,     cmOK)
   E_BUTTONUP(ID_CANCEL, cmCancel)

END_RESPONSE_TABLE


#define DLG_WIDTH  (SysFont->width*42)
#define DLG_HEIGHT (SysFont->height*14)

CustomDlg::CustomDlg() :
  TabWin(NULL, "Custom",
	 (ws.GetDeskWidth()  - DLG_WIDTH-FWIDTH*2) / 2,
	 (ws.GetDeskHeight() - DLG_HEIGHT-FWIDTH-CAPTION_HEIGHT) / 2,
	 (ws.GetDeskWidth()  + DLG_WIDTH+FWIDTH*2) / 2 - 1,
	 (ws.GetDeskHeight() + DLG_HEIGHT+FWIDTH+CAPTION_HEIGHT) / 2 - 1,
         WA_VISABLE | WA_BORDER | WA_CAPTION | WA_SAVEAREA),
  isOK(false), save(field->Custom)
{
  int ox = FWIDTH+8 + StringWidth("Height:  ", SysFont),
      oy = FWIDTH+CAPTION_HEIGHT,
      wid = 10*SysFont->width;

  WidthEdit = new Spin(this, EDIT_ID_WIDTH, "", 3, ox, oy, wid);
  WidthEdit->SetNum(field->Custom.dim.x);
  if (GameStyle == HEXAGONAL)
    hexagonfield->GetMaxWidth(field->Custom.dim.y);
  WidthEdit->SetMin(field->MinWidth);
  WidthEdit->SetMax(field->MaxWidth);
  AddAccelerator('W', WidthEdit);

  oy += (SysFont->height+4)*2;
  HeightEdit = new Spin(this, EDIT_ID_HEIGHT, "", 3, ox, oy, wid);
  HeightEdit->SetNum(field->Custom.dim.y);
  HeightEdit->SetMin(field->MinHeight);
  HeightEdit->SetMax(field->MaxHeight);
  AddAccelerator('H', HeightEdit);

  oy += (SysFont->height+4)*2;
  MineEdit = new Spin(this, EDIT_ID_MINE, "", 4, ox, oy, wid);
  MineEdit->SetNum(field->Custom.mines);
  MineEdit->SetMin(1);
  AddAccelerator('M', MineEdit);

  ox = FWIDTH+8 + StringWidth("Difficulty:  ", SysFontItallic);
  oy += (SysFont->height+4)*3 - 2;
  char temp[6];
  sprintf(temp, "%.3g", field->Custom.diff());
  Diff = new StaticText(this, temp, ox, oy, wid);

  wid = 14*SysFont->width;
  int hyt = SysFontBold->height+4;
  ox = cx2-FWIDTH-8-wid;
  oy = FWIDTH+CAPTION_HEIGHT + SysFont->height;
  Str = new TextButton(this, ID_STR, ox,oy, ox+wid-1,oy+hyt-1, "&Stored");
  oy += hyt+4;
  Beg = new TextButton(this, ID_BEG, ox,oy, ox+wid-1,oy+hyt-1, "&Beginner");
  oy += hyt+4;
  Int = new TextButton(this, ID_INT, ox,oy, ox+wid-1,oy+hyt-1, "&Intermediate");
  oy += hyt+4;
  Exp = new TextButton(this, ID_EXP, ox,oy, ox+wid-1,oy+hyt-1, "&Expert");
  oy += hyt+4;
  Ran = new TextButton(this, ID_RAN, ox,oy, ox+wid-1,oy+hyt-1, "&Random");
  oy += hyt+4;
  Res = new TextButton(this, ID_RES, ox,oy, ox+wid-1,oy+hyt-1, "Res&tore");

  oy = cy2-hyt-SysFont->height-(hyt+4);
  Sav = new TextButton(this, ID_SAV, ox,oy, ox+wid-1,oy+hyt-1, "&Save");
  oy += hyt+4;
  Del = new TextButton(this, ID_DEL, ox,oy, ox+wid-1,oy+hyt-1, "&Delete");

  wid = 10*SysFont->width;
  ox = FWIDTH+8;
  OK = new TextButton(this, ID_OK, ox, oy, ox+wid-1, oy+hyt-1, "&OK");
  Cancel = new TextButton(this, ID_CANCEL, ox+wid+10, oy,
			  ox+wid+10+wid-1, oy+hyt-1, "&Cancel");

  AddTabStop(WidthEdit);
  AddTabStop(HeightEdit);
  AddTabStop(MineEdit);
  AddTabStop(Str);
  AddTabStop(Beg);
  AddTabStop(Int);
  AddTabStop(Exp);
  AddTabStop(Ran);
  AddTabStop(Res);
  AddTabStop(Sav);
  AddTabStop(Del);
  AddTabStop(OK);
  AddTabStop(Cancel);
}


CustomDlg::~CustomDlg()
{
  delete WidthEdit; delete HeightEdit; delete MineEdit;
  delete Diff;
  delete Str; delete Beg; delete Int; delete Exp; delete Ran; delete Res;
  delete Sav; delete Del;
  delete OK; delete Cancel;
}


extern BYTE HighLightColour;

void CustomDlg::PaintWindow(int x1, int y1, int x2, int y2)
{
  TabWin::PaintWindow(x1, y1, x2, y2);

  ViewBuffer* buf = GetSubBuffer(x1, y1, x2, y2);

  const int ox = FWIDTH+8;
  int oy = FWIDTH+CAPTION_HEIGHT+SysFont->height+2;
  WriteChar(buf, ox, oy, HighLightColour, SysFontBold, 'W');
  WriteText(buf, ox+CharWidth('W', SysFontBold), oy,
		 HighLightColour, SysFont, "idth:");

  oy += (SysFont->height+4)*2;
  WriteChar(buf, ox, oy, HighLightColour, SysFontBold, 'H');
  WriteText(buf, ox+CharWidth('H', SysFontBold), oy,
		 HighLightColour, SysFont, "eight:");

  oy += (SysFont->height+4)*2;
  WriteChar(buf, ox, oy, HighLightColour, SysFontBold, 'M');
  WriteText(buf, ox+CharWidth('M', SysFontBold), oy,
		 HighLightColour, SysFont, "ines:");

  oy += (SysFont->height+4)*2;
  WriteText(buf, ox, oy, HighLightColour, SysFontItallic, "Difficulty:");

  DeleteBuffer(buf);
}


bool CustomDlg::Run()
{
  Paint();
  RefreshWindow();
  CatchMouse();
  CatchKeys();
  ws.RunEvents();
  ws.ResetEvents();
  ReleaseMouse();
  ReleaseKeys();
  if (isOK)
  {
    field->Custom.dim.x = WidthEdit->GetNum();
    field->Custom.dim.y = HeightEdit->GetNum();
    field->Custom.mines = MineEdit->GetNum();
  }
  return isOK;
}


void CustomDlg::cmOK()
{
  isOK = true;
  ws.StopRunningEvents();
}

void CustomDlg::cmCancel()
{
  isOK = false;
  ws.StopRunningEvents();
}


// Save the current dimensions before selecting one of the predefines.
void CustomDlg::SaveState()
{
  Layout temp(WidthEdit->GetNum(), HeightEdit->GetNum(), MineEdit->GetNum());
  if (!(temp == field->Beginner || temp == field->Intermediate ||
	temp == field->Expert	|| temp == field->Random))
    save = temp;
}


// Set the edit fields to those in state.
void CustomDlg::SetState(cLayout& state)
{
  WidthEdit->SetNum(state.dim.x);
  HeightEdit->SetNum(state.dim.y);
  cmChange();				// Update the mine's limits.
  MineEdit->SetNum(state.mines);
}


// Update the mine's minimum and maximum values, and the difficulty.
// In the case of hexagonal minefield, update the maximum width, since it is
// dependent on the height.
void CustomDlg::cmChange()
{
  char temp[8];
  int w = WidthEdit->GetNum(), h = HeightEdit->GetNum();
  if (GameStyle == HEXAGONAL)
    WidthEdit->SetMax(hexagonfield->GetMaxWidth(h));
  MineEdit->SetMax(field->area(Coord(w, h)) / 2);
  sprintf(temp, "%.3g", double(field->area(Coord(w, h))) / MineEdit->GetNum());
  Diff->SetText(temp);
}


extern FONT *FixedFont, *SysFont1;      // The menu uses the fixed font

// Display the menu of custom games directly underneath the "Store" button,
// a bit to the right.
void CustomDlg::cmStr()
{
  if (field->CustomPopup.number == 0)
    return;
  Layout temp = field->Custom;		// In case of Cancel
  bool rc;
  SysFont = FixedFont;
  rc = CustomPopupMenu(NULL, field->CustomPopup,
			     absx + cx2 - FWIDTH - 14*SysFont1->width,
			     absy + FWIDTH*4 + 2*SysFont1->height +
			      SysFontBold->height + 3).Run();
  CatchKeys();				// It seems PopupMenu releases keys
  SysFont = SysFont1;
  if (rc)
  {
    SaveState();
    SetState(field->Custom);
  }
  field->Custom = temp;
}

void CustomDlg::cmBeg() { SaveState(); SetState(field->Beginner);     }
void CustomDlg::cmInt() { SaveState(); SetState(field->Intermediate); }
void CustomDlg::cmExp() { SaveState(); SetState(field->Expert);       }
void CustomDlg::cmRan() { SaveState(); SetState(field->Random);       }
void CustomDlg::cmRes() {	       SetState(save);		      }


void CustomDlg::cmSav()
{
  Layout temp(WidthEdit->GetNum(), HeightEdit->GetNum(), MineEdit->GetNum());
  field->times.Add(temp);
}

void CustomDlg::cmDel()
{
  Layout temp(WidthEdit->GetNum(), HeightEdit->GetNum(), MineEdit->GetNum());
  if (field->times.Delete(temp))
    field->CustomPopup.Delete(temp);
}


/* ---------------------------	 CustomPopupRes   --------------------------- */


CustomPopupRes::~CustomPopupRes()
{
  if (number)
  {
    for (int j = 0; j < number; j++)
      delete[] menu[j].Text;
    delete[] menu;
    delete[] layout;
  }
}


// Find the layout.  If it exists, return its index; otherwise return the
// logical NOT of where it should go.
int CustomPopupRes::Find(cLayout& lo)
{
  int j;

  for (j = 0; j < number; j++)
  {
    int d = layout[j].dim.x - lo.dim.x;
    if (d == 0)
    {
      d = layout[j].dim.y - lo.dim.y;
      if (d == 0)
      {
	d = layout[j].mines - lo.mines;
	if (d == 0)
	  return j;
      }
    }
    if (d > 0)
      break;
  }

  return ~j;
}


// Insert a new layout into the custom menu, first sorting by width, then
// height and finally mines.
bool CustomPopupRes::Insert(cLayout& lo)
{
  int j, k;

  // Don't keep the predefined levels.
  if (lo == field->Beginner || lo == field->Intermediate || lo == field->Expert)
    return false;

  // Determine the position of the new layout.
  j = Find(lo);
  if (j >= 0)
    return false;
  j = ~j;

  number++;
  Layout* tl = new Layout[number];
  PopupMenuRes* tm = new PopupMenuRes[number];
  for (k = 0; k < j; k++)
  {
    tl[k] = layout[k];
    tm[k] = menu[k];
  }
  tl[k] = lo;
  tm[k].Text = new char[CustomLen];
  sprintf(tm[k].Text, "%2d x %2d, %3d mines", lo.dim.x, lo.dim.y, lo.mines);
  tm[k].ID	= k;
  tm[k].npopout = 0;
  tm[k].popout	= 0;
  while (++k < number)
  {
    tl[k] = layout[j];
    tm[k] = menu[j];
    tm[k].ID = k;
    ++j;
  }

  if (number > 1)
  {
    delete[] layout;
    delete[] menu;
  }
  layout = tl;
  menu	 = tm;

  return true;
}


// Delete a layout from the menu.
void CustomPopupRes::Delete(cLayout& lo)
{
  int j = Find(lo);
  if (j < 0)
    return;

  delete[] menu[j].Text;
  for (int k = j+1; k < number; ++k)
  {
    layout[k-1]    = layout[k];
    menu[k-1].Text = menu[k].Text;
  }
  if (--number == 0)
  {
    delete[] layout;
    delete[] menu;
  }
}


/* --------------------------	CustomPopupMenu   --------------------------- */

//DEFINE_RESPONSE_TABLE(CustomPopupMenu, AutoPopupMenu)
BOOL CustomPopupMenu::DoEvents(const event* ev)
{
  // For some reason this event doesn't get called using the standard approach
  if (ev->Type == W_COMMAND &&
      (ev->p1 >= 0 && ev->p1 < (int)popup->number))
  {
    cmSelection(ev->p1);
    return true;
  }
  if (AutoPopupMenu::DoEvents(ev))
    return true;

  E_KEYCODE(0x1b, 0x01, keyESC)
  E_LBUTTONDOWN

  return false;
}
//END_RESPONSE_TABLE


CustomPopupMenu::CustomPopupMenu(Window* parent, const CustomPopupRes& custom,
				 int x, int y) :
  AutoPopupMenu(parent, (PopupMenuRes*)custom.menu, custom.number, x, y),
  popup(&custom),
  cancelled(false)
{
}


bool CustomPopupMenu::Run()
{
  Paint();
  RefreshWindow();
  CatchMouse();
  CatchKeys();
  ws.RunEvents();
  ws.ResetEvents();
  ReleaseMouse();
  ReleaseKeys();
  return !cancelled;
}


void CustomPopupMenu::cmSelection(unsigned game)
{
  field->Custom = popup->layout[game];
  ws.StopRunningEvents();
}


void CustomPopupMenu::keyESC(int)
{
  ws.StopRunningEvents();
  cancelled = true;
}

void CustomPopupMenu::LButtonDown(int x, int y, int)
{
  if (x < absx || x > absx+w ||
      y < absy || y > absy+h)
  {
    ws.StopRunningEvents();
    cancelled = true;
  }
}
